<html>
 <head>
  <meta charset="UTF-8">
 </head>
 <body>
  <h1 data-lake-id="l92bn" id="l92bn"><span data-lake-id="ubb86a9d7" id="ubb86a9d7">典型回答</span></h1>
  <p data-lake-id="u45e19a98" id="u45e19a98"><br></p>
  <p data-lake-id="ue6a40049" id="ue6a40049"><span data-lake-id="uf304177d" id="uf304177d">ThreadLocal是java.lang下面的一个类，是用来解决java多线程程序中并发问题的一种途径；通过为每一个线程创建一份共享变量的副本来保证各个线程之间的变量的访问和修改互相不影响；</span></p>
  <p data-lake-id="u65418358" id="u65418358"><span data-lake-id="u511c7009" id="u511c7009">​</span><br></p>
  <p data-lake-id="u3828750c" id="u3828750c"><span data-lake-id="u43a8cfeb" id="u43a8cfeb">ThreadLocal存放的值是线程内共享的，线程间互斥的，主要用于线程内共享一些数据，避免通过参数来传递，这样处理后，能够优雅的解决一些实际问题。</span></p>
  <p data-lake-id="u82000dce" id="u82000dce"><span data-lake-id="u63f161b0" id="u63f161b0">​</span><br></p>
  <p data-lake-id="u70199477" id="u70199477"><span data-lake-id="ueada9b64" id="ueada9b64">比如一次用户的页面操作请求，我们可以在最开始的filter中，把用户的信息保存在ThreadLocal中，在同一次请求中，在使用到用户信息，就可以直接到ThreadLocal中获取就可以了。</span></p>
  <p data-lake-id="u354b8a4d" id="u354b8a4d"><br></p>
  <p data-lake-id="u8e4a3b74" id="u8e4a3b74"><span data-lake-id="u4b2b830e" id="u4b2b830e">ThreadLocal有四个方法，分别为：</span></p>
  <ul list="uf22bbb8c">
   <li fid="u94303cb0" data-lake-id="u55321a28" id="u55321a28"><span data-lake-id="ub195f0ca" id="ub195f0ca">initialValue</span></li>
  </ul>
  <ul list="uf22bbb8c" data-lake-indent="1">
   <li fid="u94303cb0" data-lake-id="u261b0c6d" id="u261b0c6d"><span data-lake-id="u71bd52c5" id="u71bd52c5">返回此线程局部变量的初始值</span></li>
  </ul>
  <ul list="uf22bbb8c" start="2">
   <li fid="u94303cb0" data-lake-id="u0890820d" id="u0890820d"><span data-lake-id="u15219979" id="u15219979">get</span></li>
  </ul>
  <ul list="uf22bbb8c" data-lake-indent="1">
   <li fid="u94303cb0" data-lake-id="u1ee3b4a0" id="u1ee3b4a0"><span data-lake-id="u22937c58" id="u22937c58">返回此线程局部变量的当前线程副本中的值。如果这是线程第一次调用该方法，则创建并初始化此副本。</span></li>
  </ul>
  <ul list="uf22bbb8c" start="3">
   <li fid="u94303cb0" data-lake-id="u616280a5" id="u616280a5"><span data-lake-id="u7eebcbdd" id="u7eebcbdd">set</span></li>
  </ul>
  <ul list="uf22bbb8c" data-lake-indent="1">
   <li fid="u94303cb0" data-lake-id="u1c8740cd" id="u1c8740cd"><span data-lake-id="u4e2d8ec7" id="u4e2d8ec7">将此线程局部变量的当前线程副本中的值设置为指定值。许多应用程序不需要这项功能，它们只依赖于 initialValue() 方法来设置线程局部变量的值。</span></li>
  </ul>
  <ul list="uf22bbb8c" start="4">
   <li fid="u94303cb0" data-lake-id="u73bc79f2" id="u73bc79f2"><span data-lake-id="ua96af0df" id="ua96af0df">remove</span></li>
  </ul>
  <ul list="uf22bbb8c" data-lake-indent="1">
   <li fid="u94303cb0" data-lake-id="u0a2f2930" id="u0a2f2930"><span data-lake-id="u98d935c9" id="u98d935c9">移除此线程局部变量的值。</span></li>
  </ul>
  <p data-lake-id="u3ed57cb1" id="u3ed57cb1"><br></p>
  <h1 data-lake-id="Elwq8" id="Elwq8"><span data-lake-id="u4cdcdb94" id="u4cdcdb94">扩展知识</span></h1>
  <p data-lake-id="u5728e14e" id="u5728e14e"><br></p>
  <h2 data-lake-id="OKC2P" id="OKC2P"><span data-lake-id="u5c9e834e" id="u5c9e834e">ThreadLocal的实现原理</span></h2>
  <p data-lake-id="ubfbb38be" id="ubfbb38be"><br></p>
  <p data-lake-id="u949c97a9" id="u949c97a9"><span data-lake-id="u60c0e56a" id="u60c0e56a">ThreadLocal中用于保存线程的独有变量的数据结构是一个内部类：ThreadLocalMap，也是k-v结构。</span></p>
  <p data-lake-id="u938affd4" id="u938affd4"><span data-lake-id="u07efebbf" id="u07efebbf">key就是当前的ThreadLocal对象，而v就是我们想要保存的值。</span></p>
  <p data-lake-id="u7f0afa29" id="u7f0afa29"><span data-lake-id="u748eeea6" id="u748eeea6">​</span><br></p>
  <p data-lake-id="u7b649459" id="u7b649459" style="text-align: justify"><img src="https://cdn.nlark.com/yuque/0/2023/png/5378072/1688455495250-dba1fb17-44bb-4ec7-8270-1319f708086f.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_35%2Ctext_SmF2YSA4IEd1IFA%3D%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10"></p>
  <p data-lake-id="u1dc513fb" id="u1dc513fb" style="text-align: justify"><br></p>
  <p data-lake-id="u7cbae535" id="u7cbae535"><span data-lake-id="u43a5b9de" id="u43a5b9de">上图中基本描述出了Thread、ThreadLocalMap以及ThreadLocal三者之间的包含关系。</span></p>
  <p data-lake-id="u00de29c9" id="u00de29c9"><span data-lake-id="u1c6b431b" id="u1c6b431b">​</span><br></p>
  <p data-lake-id="u5cdf5abd" id="u5cdf5abd"><strong><span data-lake-id="u5f581ff0" id="u5f581ff0">Thread类对象中维护了ThreadLocalMap成员变量，而ThreadLocalMap维护了以ThreadLocal为key，需要存储的数据为value的Entry数组。</span></strong><span data-lake-id="ue31de93d" id="ue31de93d">这是它们三者之间的基本包含关系，我们需要进一步到源码中寻找踪迹。</span></p>
  <p data-lake-id="u2183dfaa" id="u2183dfaa"><span data-lake-id="u205e2063" id="u205e2063">​</span><br></p>
  <p data-lake-id="u92d84717" id="u92d84717"><span data-lake-id="u2e21aabb" id="u2e21aabb">查看Thread类，内部维护了两个变量，threadLocals和inheritableThreadLocals，它们的默认值是null，它们的类型是</span><span data-lake-id="u9c5759a8" id="u9c5759a8">ThreadLocal.ThreadLocalMap</span><span data-lake-id="ub5de61a4" id="ub5de61a4">，也就是ThreadLocal类的一个静态内部类ThreadLocalMap。</span></p>
  <p data-lake-id="udd5cc786" id="udd5cc786"><span data-lake-id="u471e1dfb" id="u471e1dfb">​</span><br></p>
  <p data-lake-id="u8f7874ac" id="u8f7874ac"><span data-lake-id="u69f53388" id="u69f53388">在静态内部类ThreadLocalMap维护一个数据结构类型为Entry的数组，节点类型如下代码所示：</span></p>
  <p data-lake-id="uc48619fc" id="uc48619fc"><br></p>
  <pre lang="java"><code>
static class Entry extends WeakReference&lt;ThreadLocal&lt;?&gt;&gt; {
    /** The value associated with this ThreadLocal. */
    Object value;

    Entry(ThreadLocal&lt;?&gt; k, Object v) {
        super(k);
        value = v;
    }
}
</code></pre>
  <p data-lake-id="ubff3b6b5" id="ubff3b6b5"><br></p>
  <p data-lake-id="ud7b65ca9" id="ud7b65ca9"><span data-lake-id="u1e3f7fa5" id="u1e3f7fa5">从源码中我们可以看到，Entry结构实际上是继承了一个ThreadLocal类型的弱引用并将其作为key，value为Object类型。这里使用弱引用是否会产生问题，我们这里暂时不讨论，在文章结束的时候一起讨论一下，暂且可以理解key就是ThreadLocal对象。对于ThreadLocalMap，我们一起来了解一下其内部的变量：</span></p>
  <p data-lake-id="udc156de5" id="udc156de5"><span data-lake-id="ua0a7a632" id="ua0a7a632">​</span><br></p>
  <pre lang="java"><code>
// 默认的数组初始化容量
private static final int INITIAL_CAPACITY = 16;
// Entry数组，大小必须为2的幂
private Entry[] table;
// 数组内部元素个数
private int size = 0;
// 数组扩容阈值，默认为0，创建了ThreadLocalMap对象后会被重新设置
private int threshold;
</code></pre>
  <p data-lake-id="u9a5a0646" id="u9a5a0646"><span data-lake-id="udd3acce3" id="udd3acce3">这几个变量和HashMap中的变量十分类似，功能也类似。</span></p>
  <p data-lake-id="u7d006ff6" id="u7d006ff6"><span data-lake-id="uc23edbd3" id="uc23edbd3">ThreadLocalMap的构造方法如下所示：</span></p>
  <pre lang="java"><code>
/**
 * Construct a new map initially containing (firstKey, firstValue).
 * ThreadLocalMaps are constructed lazily, so we only create
 * one when we have at least one entry to put in it.
 */
ThreadLocalMap(ThreadLocal&lt;?&gt; firstKey, Object firstValue) {
    // 初始化Entry数组，大小 16
    table = new Entry[INITIAL_CAPACITY];
    // 用第一个键的哈希值对初始大小取模得到索引，和HashMap的位运算代替取模原理一样
    int i = firstKey.threadLocalHashCode &amp; (INITIAL_CAPACITY - 1);
    // 将Entry对象存入数组指定位置
    table[i] = new Entry(firstKey, firstValue);
    size = 1;
    // 初始化扩容阈值，第一次设置为10
    setThreshold(INITIAL_CAPACITY);
}
</code></pre>
  <p data-lake-id="u28272631" id="u28272631"><span data-lake-id="ua733890c" id="ua733890c">从构造方法的注释中可以了解到，该构造方法是懒加载的，只有当我们创建一个Entry对象并需要放入到Entry数组的时候才会去初始化Entry数组。</span></p>
  <p data-lake-id="uf8e91130" id="uf8e91130"><span data-lake-id="ua69605cc" id="ua69605cc">​</span><br></p>
  <h2 data-lake-id="PsUMg" id="PsUMg"><span data-lake-id="u4e3f994f" id="u4e3f994f">应用场景</span></h2>
  <p data-lake-id="uc0b1d126" id="uc0b1d126"><br></p>
  <p data-lake-id="u76899d6a" id="u76899d6a"><span data-lake-id="u109e8cc8" id="u109e8cc8">​</span><br></p>
  <h2 data-lake-id="w72ch" id="w72ch"><span data-lake-id="u7398e71a" id="u7398e71a" style="color: rgb(85, 85, 85)">ThreadLocal内存泄露问题</span></h2>
  <p data-lake-id="u48298651" id="u48298651"><br></p>
  <p data-lake-id="udcdc05be" id="udcdc05be"><br></p>
  <p data-lake-id="u1eeba493" id="u1eeba493"><br></p>
 </body>
</html>